Skip to content

feat(gpui): gpui render backend and the macos-app target with native text layout - #293

Merged
doodlewind merged 15 commits into
mainfrom
north-pancreas
Aug 18, 2026
Merged

feat(gpui): gpui render backend and the macos-app target with native text layout#293
doodlewind merged 15 commits into
mainfrom
north-pancreas

Conversation

@doodlewind

@doodlewind doodlewind commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

The DrawList grows a second backend class: gpui (Zed's Metal renderer) on macOS, beside the portable baked-text family (wgpu, software raster, GE, PPA, GLES2). Same JSX + Tailwind guest, same deterministic frame transactions and animation engine — the one capability that legitimately differs per backend, text layout, becomes an explicit contract. Full design: docs/BACKENDS.md.

The text capability contract

  • text.glyphs.baked (portable): compile-time atlases, core-owned measurement, GLYPH_RUN, byte-deterministic pixels across hosts.
  • text.layout.native (new): the host installs a core text measurer before the guest mounts (Ui::set_text_measure); taffy leaf sizes, the measureText op and painted glyphs all observe one provider — CoreText through gpui. Full OS font fallback (CJK, color emoji), no runtime atlas baking, no tofu. Pixels are deterministic per host, not across hosts — hence a different id, per the platforms.ts header rule.

Core mechanics (backend-neutral; zero behavior change until a host installs the hook):

  • DRAW_OP.TEXT_RUN (9) packs the run string's UTF-8 bytes into the word stream (8 header words + payload) — the DrawList stays the complete Vec<u32> pixel truth, so snapshots, demand-render hashes and damage word-diffs are exact by construction, never a digest.
  • One provider per node, decided at layout, correct every frame. Layout build and the draw walk accumulate the SAME gate (Resolved::declares_transform) down identical recursions and the decision is recorded on the node; when a paint-only transform changes the answer, Ui::draw re-decides and repaints before returning — zero stale frames, and canceling transforms (a parent scale inverted by a child) cannot oscillate the record. Tracked/scaled/rotated text keeps the baked pair on both sides; monospace is a real slot family (font-mono, slots 16..18, vendored JetBrains Mono) so code is monospace on every backend.
  • The raster, damage and wgpu interpreters skip the op (fixed-function backends never receive it); core coverage is at 120 tests.

The backend and host

  • engine/backends/gpui (standalone, like esp32p4-ppa): the DrawList → gpui interpreter — vector quads/gradients, baked glyph-cell blitting for portable-text apps, scissors as with_content_mask scopes, native shaping with kern/liga off (prefix-sum caret math stays exact), gouraud TRI/TEX_TRI batches rastered through pocketjs_core::raster as a pixel-exact sub-backend, shaped-line + measured-width caches.
  • hosts/macos (standalone lone-bin, like pocketbook): stock host of the macos-app target (hostAbi 3, form: "window", acceptsFixed). The profile registers exactly the host-generic surface — input.buttons, display.viewport.live (the __pocketResizeViewport hook fires for every dynamic app inside the tick transaction), text.glyphs.baked, text.layout.native. Fixed 60 Hz tick governor (never ticks from paint), demand-rendering off the DrawList hash, letterboxed fixed-viewport apps.
  • Companions are app protocol, not capabilities: the note's svc editor adapter (keyboard/pointer/IME/clipboard) rides --editor, and svcOpen is deny-by-default in pocket-ui-surface — a host answers true only for companions it explicitly declares, so an absent adapter degrades apps truthfully to standalone. Explicit companion metadata in the manifest/plan is registered debt: contracts: model companion adapters explicitly in the manifest/build plan #295.
  • bun run macos <app> (tools/macos.ts, ships with a git-checkout guard): capability-shaped flags (--fixed, --native-text) derive from the resolved plan; --editor selects the note companion (contracts: model companion adapters explicitly in the manifest/build plan #295).

Flagship: the markdown editor

apps/note runs from one source tree on every backend; on gpui it gets CoreText metrics, full CJK + color emoji input, monospace code blocks, browser-style editing polish (square-wave caret that demand rendering skips — idle repaints fell from 88% of ticks to ~2/s; double-click word selection on the virtual clock), and a real window's chrome (OS corners/resize/close — the widget-era card, grip and Close item gate off).

  • Acceptance both ways: bun run macos note --proof (scripted click + typing → autosave round-trips) and the negative proof — without --editor, svcOpen answers false and the same script produces no autosave (truthfully read-only).
  • Found & fixed en route: a guest measuring an emoji prefix sliced between UTF-16 surrogate halves crashed the frame transaction; host string ops now decode lossily (LossyString).

Benchmark

Lives in stacked #294 (harness, byte-identical Tauri/Electron comparison apps, results, fairness caveats). Headline: 1 process / 84 MB idle RSS / ~130-150 ms cold start / 10 MB disk against Electron's 5 / 382 MB / ~320 ms / 242 MB and Tauri's 4 / 193 MB / ~390 ms / 9 MB.

Review rounds — all resolved

  • R1: TEXT_RUN hash → exact bytes in words; provider recorded per node; __pocketResizeViewport host-generic; --editor decoupled from input.text.
  • R2: provider self-healing for dynamic transforms; macos-app profile narrowed to host-generic capabilities; windowed chrome; monospace slots; CI lane runs its TS surfaces.
  • R3: ONE shared transform gate + same-draw repaint (zero stale frames, no oscillation on canceling transforms); svcOpen allowlist set before mount; mono regression assertions; script/tool metadata agreement.
  • R4: deny-by-default svcOpen in the shared surface (+ note-widget declares its companion); READY behind --announce-ready; stale core comments corrected; companion-by-name registered as contracts: model companion adapters explicitly in the manifest/build plan #295; npm tarball guards in the macOS lane.

Known limitations / follow-ups

🤖 Generated with Claude Code

doodlewind and others added 8 commits August 18, 2026 06:13
The portable backend family (wgpu, software raster, PPA, GLES2) keeps
executing the DrawList against compile-time baked font atlases. This adds a
second backend class on the same DrawList contract: engine/backends/gpui
paints through Zed's gpui/Metal renderer, and text measurement + shaping
move to the host text system (CoreText) when an app opts in.

Core (backend-neutral, no behavior change unless a host installs the hook):
- text::MeasureFn — a pluggable native text measurer on Fonts;
  Ui::set_text_measure installs it before mount. measure_run, the taffy
  MeasureCtx and the measureText op all route through the one provider.
- DRAW_OP.TEXT_RUN (9): translation-only tracking-0 runs emit the run string
  + style through a DrawList side table; a styleHash word keeps identical
  word streams pixel-identical (demand-render hashes stay truthful). Tracked,
  scaled and rotated runs keep the baked GLYPH_RUN pair on BOTH the measure
  and paint sides. Partially clipped runs are scissor-bracketed.
- raster/damage/wgpu interpreters learn to skip the op (it never reaches
  fixed-function backends); 5 new core tests pin the gates.

Registry: text.layout.native capability + the macos-app target profile
(hostAbi 3, window form, acceptsFixed — the slot platforms.ts reserved), so
every fixed-viewport console demo admits unchanged, size-locked.

engine/backends/gpui (standalone, like esp32p4-ppa): the DrawList -> gpui
interpreter (quads, gradients, glyph-cell blitting for baked apps, content-
mask scissors, TEXT_RUN shaping via shape_line with kern/liga off so
prefix-sum caret math stays exact) plus a pixel-exact escape hatch: gouraud
TRI / TEX_TRI batches raster through pocketjs_core::raster into cached
local images at target density.

hosts/macos (standalone lone-bin, like pocketbook): gpui window host of the
macos-app target. Fixed 60 Hz guest ticks from a foreground timer governor
(one guest.frame + surface.tick per tick, never from paint), demand renders
off the DrawList hash, speaks note-widget's svc editor protocol (keyboard,
pointer, scroll, clipboard, IME through EntityInputHandler), letterboxes
fixed-viewport apps. bun run macos <app> resolves the manifest against
macos-app and derives every host flag from the plan.

Also: host string ops now decode JS strings lossily (LossyString) — an app
measuring an emoji prefix sliced between surrogate halves is legal JS and
must never abort the frame transaction.

apps/note enhances text.layout.native: the same unmodified JSX markdown
editor now runs with CoreText metrics, full CJK + color emoji, no runtime
atlas baking. Proof: bun run macos note --proof (scripted click + typing,
debounced autosave round-trips through the gpui host).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tools/bench-desktop.ts measures the pocket note on the gpui host against
byte-identical web editors shelled by Tauri v2 and Electron: cold start to
each app's own first-painted-frame READY report, hands-off idle and a
120 chars/s typing storm through each stack's real edit path, ps process-
tree medians (WebKit XPC helpers attributed by spawn-delta — Tauri's
WebContent/GPU processes are launchd children), footprint for physical
memory. The gpui renderer gains shaped-line and measured-width caches so a
keystroke repaint reshapes one line, not the document. docs/BACKENDS.md
names the backend split; .github/workflows/macos.yml is the first macOS CI
lane (core tests + clippy + host build).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nch fixtures

Final measured run (M3 Max, methodology + fairness caveats in the report):
the pocket note on the gpui host takes 1 process / 84 MB idle RSS / 145 ms
cold start / 10 MB disk against Tauri v2 (4 procs, 192 MB, 391 ms) and
Electron (5 procs, 382 MB, 328 ms, 242 MB). Storm completion is now
verified (STORM-DONE), the Tauri window is explicitly focused (an
unfocused WKWebView throttles timers and reads fiction), and the
footprint column is dropped from the table — Electron's hardened helpers
refuse task inspection, so RSS is the uniform metric.

The benchmark fixtures' cargo target/ was packing into the npm tarball
through the wholesale "tools" files entry (the v0.8.0 E415 failure mode —
the tests/npm-package.test.ts tripwire caught it): tools/bench-desktop is
now a governed negation in the files map. Tauri codegen (gen/) untracked;
gpui backend passes clippy -D warnings for the macOS CI lane.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The caret was animate-pulse — a continuous opacity sine that changes the
DrawList every tick, so an idle editor repainted at 60 fps on every host
(88% of ticks; ~3.6% CPU on both the gpui and wgpu hosts). Replace it with
a browser-style square wave (app-local pocket.config.ts theme: 500 ms on /
500 ms off, hard edges via a same-frame twin keyframe) plus the browser
input discipline: the caret is SOLID while typing or moving and resumes
blinking from its ON phase after a 0.6 s rest (a <Show> swap remounts the
animated node, restarting the baked timeline at frame 0).

The square wave's constant segments keep the DrawList byte-stable between
edges, so every demand-rendering host skips them: idle-in-edit repaints
drop from 2115/2400 ticks to 83/2400 (~2 fps, exactly the edge count), CPU
from 3.6% to 0.95% on the gpui host and 3.65% to 0.7% on the wgpu widget
host. Same measurement protocol as docs/bench.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Double click selects the word under the pointer in BOTH modes, browser
semantics throughout: whitespace and punctuation select as runs, a click
just past a word's right half still selects the word (caretFromX rounds to
the nearest boundary), CJK and surrogate pairs ride the non-ASCII word
class, code blocks stay atomic in preview (rowSelSpan granularity), and a
drag after the double click extends from the word start. Detection rides
the virtual clock (0.4 s / 3 px on the svc press stream) so replays stay
deterministic; wordRangeAt is regex-free QuickJS-portable math with unit
coverage.

The sample doc's 'same bytes as the PSP build' line predates the backend
split — it now names the real contract (same core, same DrawList; wgpu
paints baked atlases, gpui paints native text) and the two launch
commands, and the charset-anchor comment drops the stale tofu caveat
(runtime glyph baking and native shaping both exist now).

Verified end-to-end on the gpui host: scripted double click on 'Pocket' +
typing X autosaves '# X Note'.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… svc-independent capabilities

Three review blockers on #293, each fixed at the contract level:

1. TEXT_RUN carried a 32-bit content hash while the run string lived in a
   side table — colliding texts could leave identical word streams with
   different pixels, silently defeating demand-render hashes and damage
   word-diffs, and breaking the "DrawList is the complete Vec<u32>"
   contract. The op now packs the run string's UTF-8 bytes INTO the words
   (8 header words + payload; slot/align/lineHeight all in-stream) and the
   side table is gone: snapshots, hashes and diffs are exact by
   construction, not probabilistic. All interpreters skip the variable
   length; the gpui backend decodes from the stream.

2. Measurement and paint could disagree on rotated/scaled text (native-
   sized box, baked glyphs). The provider is now chosen ONCE at layout
   build (native iff a measurer is installed, tracking is 0 and the
   subtree declares no non-translation transform) and RECORDED on the node
   (Node::text_native); paint follows the record unconditionally, and the
   style-dirty restyle path can only flip it for a node-local tracking
   change. Rotated/transformed text takes the baked pair on BOTH sides —
   pinned by tests including a transformed-ancestor case and a
   paint-only-transform consistency case.

3. macos-app capabilities were conflated with the note's svc protocol.
   display.viewport.live is now host-generic: every dynamic app receives
   the framework's __pocketResizeViewport hook inside the tick transaction
   (the hero-resize regression); --editor derives from the app being the
   note companion, never from input.text; and the registry comment states
   the delivery paths plainly — buttons + live viewport host-generic,
   pointer/text/IME/clipboard via the companion adapter today (the
   macos-widget stock-host bar), with the host-generic pointer feed named
   as follow-up (the touch packing's 9-bit axes cannot carry a 4096-px
   window, so it needs new framework surface).

Rot: hosts/macos passes clippy -D warnings and CI now runs it; the macOS
workflow path filter covers contracts/spec, tools/macos.ts and apps/note;
tools/macos.ts is negated out of the npm tarball (its build inputs are
git-only — a published entry point that cannot run).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Tauri/Electron comparison apps, the runner and the results move to a
stacked PR so the backend/host/contract surface reviews on its own
boundary. The npm files negation stays — it guards the re-add.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@doodlewind

Copy link
Copy Markdown
Collaborator Author

三个 P1 均已在 17e1881 修复,rot 项一并处理,benchmark 已拆分为 stacked #294。逐条回应:

1. TEXT_RUN 哈希碰撞 → 词流现在直接携带字节。 你的判断成立:短哈希不能替代精确内容,且违反了 "DrawList 是完整 Vec" 的契约。修复取消了 side table 和 styleHash:TEXT_RUN 现在把 run 字符串的 UTF-8 字节打包进词流(8 个头部词 + payload,slot/align/lineHeight 全部随流),所有 hash/damage/快照消费者天然精确——是字节比较,不是摘要。各解释器按变长跳过;text_run_words_are_the_complete_pixel_truth 钉住该性质。你构造的碰撞对现在两帧词流必然不同。

2. 测量/绘制 provider 不一致 → provider 在布局构建期决策一次并记录在节点上。 原实现确实矛盾(native 高度布局 + baked 图集绘制,测试还钉错了语义)。现在 layout.rs build() 决策(native ⇔ 测量器已装 ∧ tracking=0 ∧ 子树未声明非平移 transform),记录为 Node::text_native,绘制无条件跟随记录;style-dirty 增量路径只允许节点本地 tracking 变化翻转记录。旋转/缩放/tracking 文本在两侧一致地走 baked 对——tracked_and_transformed_runs_use_the_baked_pair_on_both_sides(含祖先 transform 用例)与 recorded_provider_survives_a_paint_only_transform(绘制期 transform 不得撕裂已记录的 pair)钉住。

3. 能力与 Note 协议解耦。

  • hero resize 回归已修:每个动态视口应用在 tick 事务内收到框架的 __pocketResizeViewport 钩子(不再只改 Rust core);note 的 svc resize 只是其 companion 方言的叠加,幂等。
  • --editor 不再由 input.text 推导:tools/macos.ts 明确它是 pocket-note 的 companion 适配器(app 协议,非能力)。
  • 指针的宿主级通用化是真实的后续工作而非疏忽:现有 touch 打包是 9 bit/轴(≤511 逻辑像素),装不下 app-frame 窗口,需要新的框架层绝对指针通道。在此之前 pointer/text/IME/clipboard 经 companion 适配器交付——与 macos-widget 在 feat(pocket-widget): flat widget runtime — Pocket Note, a markdown sticky on the desktop #129 确立的 stock-host 标准相同,registry 注释已逐能力如实写明交付路径。

Rot 项: hosts/macos 过 clippy -D warnings 且 CI 现在跑它;workflow 路径过滤覆盖 contracts/spec/**tools/macos.tsapps/note/**!tools/macos.ts 负排除出 npm tarball(其构建输入 git-only,发布入口无法运行的问题成立);benchmark 工具+对比应用+结果整体移至 #294(基于本分支 stack,本分支保留 !tools/bench-desktop 负排除守卫合并后的 target/ 打包风险)。

验证:core 119 测试、bun run test 11/11 stage、engine workspace 30 个 test-result、双 clippy、tsc、tarball 守卫、bun run macos note --proof 全绿。

… profile, windowed chrome, monospace code

Review blockers:

1. Paint-only transforms (rotate/scale never relayout) could leave a text
   node's recorded provider permanently stale — native-measured box painted
   forever unrotated, and no way back. The draw walk now DETECTS the
   divergence (desired provider from the live world transform vs the
   record) and schedules the relayout that re-decides the pair: the stale
   frame lasts at most one tick, in both directions, with the 3D subtree
   path exempt (always the baked pair). Pinned by
   provider_self_heals_after_a_paint_only_transform.

2. macos-app declared input.text/pointer/ime/host.clipboard while only the
   note companion delivered them. The profile now registers exactly what
   the host implements for EVERY app — input.buttons, display.viewport.live,
   text.glyphs.baked, text.layout.native — and the registry comment names
   the companion delivery for the rest. The note's edit/pointer gates now
   track the COMPANION's runtime presence (connectSvc() !== null), not a
   capability id, so the flagship behaves identically while the contract
   stops over-promising.

Flagship polish (user-reported):

- Widget-era chrome is gated on platform.target === "macos-widget": a real
  window keeps OS corners (no rounded-xl card), resizes at its edges (no
  grip dots) and closes from its titlebar (no "Close widget" menu item).
- Markdown code is monospace on every backend: MAX_FONT_SLOTS grows 16→24
  with mono slots 16..18 (font-mono, 12/14/16 px), baked from a vendored
  JetBrains Mono (OFL) on the portable side and mapped through the host
  text system on gpui. The note's fenced blocks and inline code move to
  slot 17; note-widget's cjk mirror learns the mono rows.

Rot: both new crates pass cargo fmt --check and CI enforces it; the macOS
lane now actually exercises its TS path filter (contract drift, platform/
note/font-bake tests, and a real macos-app plan+bundle build of the note);
docs/BACKENDS.md describes the bytes-in-words TEXT_RUN and the recorded
provider instead of the deleted side table.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@doodlewind doodlewind changed the title feat(gpui): gpui render backend, macos-app target with native text layout, and the desktop benchmark feat(gpui): gpui render backend and the macos-app target with native text layout Aug 18, 2026
@doodlewind

Copy link
Copy Markdown
Collaborator Author

第二轮两个 P1 + 收尾项已在 0340e8d 处理:

1. 动态 transform 的 provider 失效 → 绘制期检测 + 自愈。 你的判断成立:"consistency beats fidelity" 把一次性权衡固化成了永久错误,且反方向(transform 移除后)永远回不到 native。现在 draw walk 在每个文本节点上比较"记录的 provider"与"当前 world 变换所要求的 provider"(3D 子树豁免),发现分歧即置脏——下一 tick 的 relayout 让测量与绘制一起重新决策。陈旧对最多存活一帧,双向收敛;穿越 identity 的循环动画每周期恰好重决策两次(注释里写明)。原测试改为钉住收敛语义:provider_self_heals_after_a_paint_only_transform(rotate 30° 进、rotate 0 出,两个方向都断言盒高与 op 同步翻转)。

2. macos-app 虚报能力 → 选了"从 profile 移除"分支。 现在 profile 只注册宿主对每个应用都实现的面:input.buttonsdisplay.viewport.livetext.glyphs.bakedtext.layout.native。你构造的"非 Note 应用申请四项能力"现在 resolver 会如实返回 false。配套修复:note 的编辑/指针门从 hasFeature(...) 改为跟随 companion 的运行时存在connectSvc() !== null)——行为不变,契约不再替 companion 背书。把 companion adapter 显式建模进 manifest/plan 仍是命名的后续项。

次要项:

  • CI 绿灯幻觉:macOS lane 现在真正执行其 path filter 覆盖的 TS 面——bun tests/contract.ts + platform/note/font-bake 三个测试文件 + 一次真实的 macos-app plan+bundle 编译,另加两 crate 的 cargo fmt --check(均已格式化)。
  • docs/BACKENDS.md 已重写为 bytes-in-words TEXT_RUN + recorded-provider 语义;PR 标题去掉 benchmark。
  • published macos script:note/widget/e2e 等脚本同样引用 repo-only 宿主,这是仓库既有形态;无法运行的工具本体已被 !tools/macos.ts 排除,scripts 不是 npm 包的消费面——记录为已考虑、暂不改。
  • future-incompat(block/proc-macro-error2)来自 gpui 依赖树上游,非本 PR 可修,认领为技术债。

顺带的旗舰修缮(Evan 直接反馈): widget 时代的圆角卡片/右下 grip/"Close widget" 菜单项在 macos-app 上按 platform.target 门控消失(真窗口由 OS 提供角、缩放与关闭);markdown 代码块与行内代码在所有后端变为等宽——MAX_FONT_SLOTS 16→24,新增 font-mono 槽位 16..18,portable 侧烘焙 vendored JetBrains Mono(OFL),gpui 侧经宿主文字系统映射同族。

验证:core 119、bun run test 11/11、tsc、双 clippy -D warnings、双 fmt --check、tarball 守卫、--proof 全绿;窗口截图确认方角/等宽/无 grip。

doodlewind and others added 4 commits August 18, 2026 17:03
note.test.ts boots the sim host, which builds engine/wasm — the runner
needs wasm32-unknown-unknown (and fmt --check needs rustfmt declared).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…adding

The POCKET NOTE label is widget identity — a real window already titles
itself in the OS titlebar, so the wordmark hides with the rest of the
widget chrome (the eye/pencil toggle and menu stay). The content column's
minimum side padding rises 22 -> 28 px, the floor narrow windows pin to,
so text stops hugging the window edge; wide windows keep the centered
560 px column.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The POCKET NOTE wordmark hides off macos-widget now, and the sim boot is
not a widget — assert the sample document instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… truthful svcOpen

1. The provider divergence check compared unlike predicates: layout gated
   on declared transforms down the path, the draw walk on the composed
   matrix being a translation — a parent scale canceled by a child's
   inverse satisfied one and not the other, oscillating layout.dirty into
   a permanent relayout loop, and every transform transition still painted
   one stale frame. Both sides now accumulate ONE shared predicate
   (Resolved::declares_transform) down identical recursions, so divergence
   can only mean a transform VALUE changed since the last relayout — and
   Ui::draw re-decides and REPAINTS before returning, so every frame that
   leaves draw() is provider-correct. Pinned by
   canceling_transforms_do_not_oscillate_the_provider (draws must not
   schedule spurious relayouts) and the zero-stale-frame rewrite of the
   transform enter/exit test.

2. svcOpen answered true for ANY service (allowlist default None), so the
   note believed its companion was live even when the host was launched
   without --editor — edit/pointer UI over a channel nobody feeds. The
   host now sets the allowlist before mount: exactly "note" with the
   adapter on, empty otherwise. Verified both ways: without --editor the
   scripted pencil-click + typing produces NO autosave (truthfully
   read-only); the --proof acceptance still passes with it.

Minors: the macOS lane's path filter covers framework/compiler, the fonts,
and the test files it executes; font-bake gains mono regression assertions
(slot table stability + the monospace property itself: uniform baked
advances where the proportional face differs); tools/macos.ts ships again
with a git-checkout guard (the note.ts precedent — script and tool now
agree) instead of the contradictory script-without-tool metadata;
BACKENDS.md points at #294 for the benchmark; stale styleHash/bench
comments in the host corrected; the vendored license loses its trailing
whitespace.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@doodlewind

Copy link
Copy Markdown
Collaborator Author

第三轮两个 P1 已在 cca2d6e 修复:

1. provider 门谓词不等价 → 统一为单一共享谓词 + 同帧重试(零错误帧)。 你构造的抵消变换(父 scale-200 × 子 scale-50)确实会让两侧永不一致并形成持续 relayout——谓词分裂是我的设计错误。现在布局构建与绘制遍历沿完全相同的递归累积同一个谓词Resolved::declares_transform,声明路径而非合成矩阵),失配在数学上只可能来自"上次布局后 transform 值变了";而 Ui::draw 检测到失配后在返回前重排并重画(单次重试必然稳定,因为重建后的记录就是重画所期望的)——每一帧离开 draw() 时都是 provider 正确的,不再有"接受一帧错误"的测试。钉住:canceling_transforms_do_not_oscillate_the_provider(含连续多次 draw 断言不再产生虚假 relayout 调度)、transform 进入/退出的零错误帧重写版。

2. svcOpen 运行时虚报 → 宿主在 mount 前显式设置 allowlist。 --editor 时精确放行 "note",否则空表。双向实测:无 --editor 时脚本点铅笔 + 打字不产生任何自动保存(app 如实回退只读,svcOpen 返回 false);--proof 正常通过。把 adapter 建模进 plan(摆脱 manifest.name)仍列为后续项。

次要项:

  • macos.yml path filter 补上 framework/compiler/**assets/fonts/** 与被执行的三个测试文件。
  • font-bake 新增 mono 回归断言:槽位表稳定性(既有槽位号不动)+ 等宽性质本身(烘焙后所有字形 advance 相等,同字符集下比例字体必然 >1 种)。
  • script/tool 自相矛盾:采纳一致化方向但反向执行——tools/macos.ts 重新入包并加 git-checkout 守卫(与 tools/note.ts 完全同型:wrapper 入包、宿主 git-only、缺输入时给出明确指引而非 cargo 报错)。script 与 tool 现在一致。
  • BACKENDS.md 的 benchmark 指向改为 feat(bench): desktop editor benchmark — gpui vs Tauri v2 vs Electron #294;宿主内残留的 styleHash/bench 注释已更正;license 行尾空格已去。
  • PR 体量:mono 槽位扩容与 Note polish 来自 Evan 对旗舰的直接反馈,与 backend 评审同轮发生;如你认为有必要我可以把 font-mono(spec/tailwind/baker/assets)再拆一层 stacked PR——按你 round 3 的"非功能阻塞"判断暂未拆。

验证:core 120、bun run test 11/11、workspace 30 组、tsc、双 clippy、双 fmt、tarball 守卫、正负两个 proof、git diff --check 干净。

…ADY, honest comments

- pocket-ui-surface: svcOpen now DENIES by default (the allowlist is a
  plain list, empty unless the host declares its companions) — the shared
  API is truthful-by-default instead of relying on every future host to
  remember the footgun. note-widget declares its "note" companion
  explicitly; a new surface test pins the default-deny.
- The host's READY first-frame marker moves behind --announce-ready (the
  benchmark runner's flag, PR #294) — a production launch prints nothing.
- The two core comments still describing a one-frame-stale provider now
  state the same-draw re-decide + repaint the code actually performs.
- Companion-by-manifest-name is registered as architecture debt
  (issue #295) and referenced where the convention lives (tools/macos.ts).
- The macOS lane watches package.json + tests/npm-package.test.ts and runs
  the npm tarball guards.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
doodlewind added a commit that referenced this pull request Aug 18, 2026
The benchmark from #293, split out for review-boundary hygiene: byte-
identical web editors shelled by Tauri v2 and Electron against the pocket
note on the gpui host, measured by tools/bench-desktop.ts (process-tree ps
medians, WebKit XPC spawn-delta attribution, per-app first-frame READY,
120 chars/s typing storm through each stack's real edit path). Results +
fairness caveats in docs/bench/gpui-vs-tauri-electron-2026-08-18.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@doodlewind

Copy link
Copy Markdown
Collaborator Author

第四轮残留已全部处理(主分支尾部提交 + bench 分支 rebase):

  • 陈旧注释:draw.rs 两处 "stale for one frame" 注释改为与实现一致的"同一 draw 内重排重绘、无陈旧帧出 draw()"表述。
  • companion-by-name:登记为架构债 contracts: model companion adapters explicitly in the manifest/build plan #295(manifest/plan 显式 companion 元数据),tools/macos.ts 的注释直接引用该 issue,并明确标注 --editor 是"唯一不来自 plan 的 flag"。PR 描述同步改口。
  • allowlist 默认值:做了结构性修复而非注释——pocket-ui-surface 的 svcOpen 现在默认拒绝(allowlist 是普通列表,宿主必须显式声明它服务的 companion)。note-widget 补上了 ["note"] 声明;新增 surface 测试钉住 default-deny。未来宿主无法再重犯这一类。
  • CI 覆盖:macos.yml 监听 package.json 与 tests/npm-package.test.ts,并在 lane 内运行 npm tarball 守卫。
  • READY/storm:READY 移到显式 --announce-ready 之后(feat(bench): desktop editor benchmark — gpui vs Tauri v2 vs Electron #294 的 runner 已在其分支加传该 flag 并 rebase);--storm 本就是显式 flag,二者现在都不会在生产启动路径产生任何输出。
  • PR 描述:整体重写——零陈旧帧表述、note "同一份源码跨后端 + 本 PR 的产品打磨"的准确措辞、120 tests、tools/macos.ts 入包+守卫、capability flags 来自 plan / --editor 为 companion(contracts: model companion adapters explicitly in the manifest/build plan #295)、benchmark 表移除只留 feat(bench): desktop editor benchmark — gpui vs Tauri v2 vs Electron #294 指针、四轮 review 决议汇总。

验证:surface 6 测试(含 default-deny 新测试)、core 120、bun run test 11/11、正负双 proof(deny-by-default 下重验)、双 clippy/fmt、note-widget 重编译通过。

…honest flag comments

docs/BACKENDS.md states the actual invariant (same-draw relayout+repaint,
zero stale frames — not next-tick healing); the macOS lane runs the
pocket-ui-surface tests (svcOpen deny-by-default landed there); the
tools/macos.ts flag comment names its two non-plan derivations and points
both at #295, which also gains the plan-completeness (viewport policy)
and Apple-sidecar-allowlist items.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@doodlewind

Copy link
Copy Markdown
Collaborator Author

第五轮两个必修项 + 三个债务标注已处理(尾部提交):

  1. docs/BACKENDS.md 不变量表述:改为实际实现——同一 draw() 内 relayout + repaint,零陈旧帧,共享谓词无振荡。核心不变量的文档与代码现在逐字一致。
  2. surface 单测入 CI:macOS lane 新增 cargo test --locked --manifest-path engine/Cargo.toml -p pocket-ui-surface(default-deny 与 LossyString 测试所在地)。

债务标注:

验证:tsc、--proof、CI 将随本推送重跑。

@doodlewind
doodlewind marked this pull request as ready for review August 18, 2026 10:07
@doodlewind
doodlewind merged commit 81bea09 into main Aug 18, 2026
3 checks passed
@doodlewind
doodlewind deleted the north-pancreas branch August 18, 2026 10:07
doodlewind added a commit that referenced this pull request Aug 18, 2026
The benchmark from #293, split out for review-boundary hygiene: byte-
identical web editors shelled by Tauri v2 and Electron against the pocket
note on the gpui host, measured by tools/bench-desktop.ts (process-tree ps
medians, WebKit XPC spawn-delta attribution, per-app first-frame READY,
120 chars/s typing storm through each stack's real edit path). Results +
fairness caveats in docs/bench/gpui-vs-tauri-electron-2026-08-18.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
doodlewind added a commit that referenced this pull request Aug 18, 2026
…294)

* feat(bench): desktop editor benchmark — gpui vs Tauri v2 vs Electron

The benchmark from #293, split out for review-boundary hygiene: byte-
identical web editors shelled by Tauri v2 and Electron against the pocket
note on the gpui host, measured by tools/bench-desktop.ts (process-tree ps
medians, WebKit XPC spawn-delta attribution, per-app first-frame READY,
120 chars/s typing storm through each stack's real edit path). Results +
fairness caveats in docs/bench/gpui-vs-tauri-electron-2026-08-18.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(bench): re-measure on the review-fixed build, settle before idle sampling

Numbers now come from the post-review TEXT_RUN code. The runner settles
20 s after READY before idle sampling (pcpu is a decaying average — early
samples carried launch work into every app's idle median), and the report
reads idle through the structural metric: the pocket governor receipt's
repaint rate (84/2400 idle ticks, the caret square wave's edge count),
since pcpu medians at low single digits drift ±1.5 points across runs for
pocket and Tauri alike.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* bench: pass --announce-ready (the READY marker is opt-in on the host now)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
doodlewind added a commit that referenced this pull request Aug 19, 2026
…anifest and plan (#297)

Closes #295. The companion adapter was selected by an app-name convention
(tools/macos.ts matched manifest.name) and hosts derived size-locking by
re-reading the raw manifest — the resolved plan was not the complete host
boot truth. Now:

- pocket.json gains app.companions: the exact svcOpen service names the
  app's adapters speak (schema-validated kebab names, unique). The note
  declares ["note"].
- The resolved plan carries `companions` and `viewport.policy`
  ("fixed" | "dynamic" — which manifest variant the target resolved), so
  every host flag derives from one artifact: tools/macos.ts drops both the
  manifest.name convention and the manifest re-read, passes --companions
  from the plan, and the host builds its svcOpen allowlist from exactly
  that list (deny-by-default underneath, unchanged).
- The Apple sidecar gains its allowlist declaration surface:
  pocket_apple_set_svc_allowlist (before eval_bundle, like set_identity) —
  the deny-by-default gap flagged in #293 round 5.
- Schema JSON regenerated from the TypeScript source; plan fixtures
  regenerated; the E7/device-profile plan expectations pin the new policy
  field (E7 is a window form — its plans are dynamic-policy).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant